package com.wili.c.asm;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import com.c.Ca;
import com.wili.c.Class9;
import com.wili.c.File9;
import com.wili.c.File9.TranverseFun;
import com.wili.c.Misc9;
import com.wili.c.Src9;
import com.wili.c.Str9;
import com.wili.extract_srcFile.java.RootOfClsMember;
import com.wili.insertLog.Catt;
import _a.Logu;
/**
 * 	实现功能：扫描所有给定属性的赋值语句，自动插入日志，使运行时能输出属性值。<br>
 * 	删除日志：Coms2b.removeLog_fromDirs();
 * 	@author weila 2021年3月7日
 */
public class FindPutFieldFromClassFile {
	final List<ClsAndFieldSet> fieldsList = new ArrayList<>();

	final List<File> clsRootDirs = new ArrayList<>();
	static class FieldSet {//纳入当前追踪集的属性
		public List<FieldItem> clsList = new ArrayList<>();

		public Set<String> fdList = new HashSet<>();
	}
	public FindPutFieldFromClassFile(File... dirs) {
		for (File dir : dirs) {
			this.clsRootDirs.add(dir);
		}
	}

	public void addJars(File... jarFiles) {
		for (File dir : jarFiles) {
			this.clsRootDirs.add(dir);
		}
	}

	public void abc(Class<?> clazz, String... fields) {
		Str9.nl2 = "";
	}
	static File toFile = new File(Logu.ConfRoot, FindPutFieldFromClassFile.class.getSimpleName());

	static final String QT = "\"";

	static final String InsertSt = Logu.class.getName() + ".log(1,";

	static int logNum;
	/**
	 * 	插入日志
	 */
	static void insertLogIntoFile(Map<Class<?>, List<PutFieldItem>> map, File... sourceDirs) {
		String pat = ("(\\w+[^\\s]+)\\s*=");
		for (Map.Entry<Class<?>, List<PutFieldItem>> ent : map.entrySet()) {
			Class<?> cls = ent.getKey();
			if (!Logu.isAcceptPackage(cls))
				continue;
			Class<?> fileCls = Class9.fileClassOf(cls);
			File javaFile = File9.javafileOf(null, fileCls, sourceDirs);
			if (javaFile == null || !javaFile.exists()) {
				String msg = "Not found file:" + fileCls.getSimpleName() + ".java in root dirs:"
						+ Misc9.concatArr(sourceDirs, "\n", 0);
				throw new RuntimeException(msg);
			}
			String fileSt = File9.strOfFile(javaFile);
			RootOfClsMember root = Src9.newRootOfClsMember(cls, fileSt);
			List<Integer> indList = Misc9.indexListOfLines(fileSt);
			StringBuilder sb = new StringBuilder((int) (fileSt.length() * 1.1));
			int from = 0;
			List<PutFieldItem> lis = ent.getValue();
			Collections.sort(lis);
			for (int k = 0; k < lis.size(); k++) {
				PutFieldItem putField = lis.get(k);
				int lineStart = indList.get(putField.lineNo - 2);
				boolean isField = root.isFieldAtLineNo(putField.lineNo);
				String line = fileSt.substring(lineStart, indList.get(putField.lineNo - 1));
				sb.append(fileSt.substring(from, lineStart));
				sb.append(line);
				String line2 = Str9.trimL(line);
				String dent = line.substring(0, line.length() - line2.length());
				if (line.endsWith(";")) {
					String leftExpr = Str9.findPat(pat, 1, line, 0);// leftExpr可能出错
					++logNum;
					String logSt = InsertSt + logNum + "," + QT + putField.field + QT + "," + leftExpr + ");";
					if (isField) {
						sb.append(dent + "{ " + logSt + " }");
					} else {
						sb.append(dent + logSt);
					}
				}
				from = indList.get(putField.lineNo - 1);
			}
			sb.append(fileSt.substring(from));
			String st2 = sb.toString();
			File9.str2file(st2, javaFile, null, false);
			Ca.warn(1, lis.size() + " times insertLogIntoFile: " + javaFile.getName());
		}
	}

	/**
	 * 	扫描所有给定属性的赋值语句，自动插入日志（使运行时能输出属性值）
	 */
	public Map<Class<?>, List<PutFieldItem>> startFind(Class<?> findClass, String findField) {
		this.fieldsList.add(new ClsAndFieldSet(findClass, findField));
		FieldItems retFieldItmes = new FieldItems();
		int depth = Logu.logRelatedField ? 2 : 1;
		for (int i = 0; i < depth; i++) {
			if (i == 1) {
				Ca.warn(1, "find related field...");
				classNo = 0;
				fieldsList.clear();
				for (Class<?> cls : retFieldItmes.getFieldMap.keySet()) {
					Set<String> fds = retFieldItmes.getFieldMap.get(cls);
					fieldsList.add(new ClsAndFieldSet(cls, fds));
				}
				retFieldItmes.getFieldMap = null;
			}
			for (File dir : clsRootDirs) {
				if (dir.isDirectory()) {
					String rootPath = dir.getAbsolutePath().replace(File.separatorChar, '.');
					File9.traverseFile(dir, 0, new TranverseFun() {
						@Override
						public void doFun(int depth, File clsFile) {
							if (!clsFile.getName().endsWith(".class"))
								return;
							if (++classNo % 100 == -10)
								Ca.debug(1, classNo);
							String pkgNam = Class9.packageNameOfClassFile(rootPath, clsFile);
							Class<?> clazz = Class9.classOfClassFile(pkgNam, clsFile);
							{//排除类型
								if (!Logu.isAcceptPackage(clazz))
									return;
								if (clazz.equals(Logu.class) || clazz.equals(FindPutFieldFromClassFile.class))
									return;
								if (Catt.class.equals(clazz))
									Ca.pause();
								if (++clsFileNo % 500 == 0)
									Ca.warn(1, "\t" + clsFileNo + " class...");
								Ca.debug(1, clsFileNo + ", find in ", clsFile.getAbsoluteFile() + ", ...");
							}
							for (int k = 0; k < fieldsList.size(); k++) {
								ClsAndFieldSet item = fieldsList.get(k);
								Asm9.findPutField(clsFile, clazz, item.cls, item.fieldSet, retFieldItmes);//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
							}
						}
					});
				} else {
					Misc9.traverseClassInJarFile(dir, new Function<Class<?>, Boolean>() {
						@Override
						public Boolean apply(Class<?> clazz) {
							for (int k = 0; k < fieldsList.size(); k++) {
								ClsAndFieldSet item = fieldsList.get(k);
								if (++classNo % 100 == -10)
									Ca.debug(1, classNo);
								Asm9.findPutField(null, clazz, item.cls, item.fieldSet, retFieldItmes);
							}
							return true;
						}
					});
				}
			}
		}
		return retFieldItmes.setFieldMap;
	}
	static class ClsAndFieldSet {
		public Class<?> cls;

		public Set<String> fieldSet;
		public ClsAndFieldSet(Class<?> cls, String field) {
			this.cls = cls;
			this.fieldSet = new HashSet<>();
			fieldSet.add(field);
		}

		public ClsAndFieldSet(Class<?> cls, Set<String> fields) {
			this.cls = cls;
			this.fieldSet = fields;
		}
	}
	/**
	 * 	从字节码中查找所有对指定属性的赋值，然后插入变量打印日志。
	 */
	public static void findField_then_insertLog(int insertLog, Class<?> clazz, String field) {
		File classRootDir = Class9.jarFileOrClassRootOf(clazz);
		if (classRootDir.isFile())
			return;
		//Ca.warn(1, "In source dirs:" + Misc9.strOfObjs("\n\t", Logu.getRootDirs_ofSourceFile()));
		Ca.warn(1, "Start findField_then_insertLog：" + clazz.getName() + "." + field);
		FindPutFieldFromClassFile find = new FindPutFieldFromClassFile(classRootDir);
		Map<Class<?>, List<PutFieldItem>> map = find.startFind(clazz, field);//<<<<<<<<<<<<<<<<<<<<<<<<<<<<
		if (map.isEmpty()) {
			Ca.warn(1, " Not found fields:" + field + " in class " + clazz.getName());
			return;
		}
		Ca.warn(1, "Find these fields:\n" + Misc9.strOfList(map.values(), -1, "\n\t"));
		if ("sfd".isEmpty())
			Misc9.printMap(map);
		if ("".isEmpty() && insertLog == 1) {
			File9.writeObject(map, toFile, -1);
			insertLogIntoFile(map, SourceRootDirs);//<<<<<<<<<<<<<
		} else {
		}
	}
	public static File[] SourceRootDirs;
	public static void main(String[] args) {
		Logu.main(args);
		System.out.println("end.");
	}
	static int classNo, clsFileNo;
}
